﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using MetaCollection.Framework.Repository;

namespace MetaCollection.Framework.Provider
{
	/// <summary>
	/// Provides Tracing functionality on All Request Events.
	/// </summary>
	/// <remarks>In the interests of reducing the required runtime trust level, all System.Diagnostics code is 
	/// removed via a compilier switch when not compiling for Debug.  This of course means that the TraceProvider
	/// will do nothing when compiled in release mode.</remarks>
	public class TraceProvider : IProvider
	{
		/// <summary>
		/// Unique identifier of the instance of the resouce provider.
		/// </summary>
		private Guid m_Id = new Guid("{524C1903-9E23-4e82-BBB0-0F6E97E74960}");
		/// <summary>
		/// Friendly name of the resource type being provided
		/// </summary>
		private string m_Name = "TraceProvider";
		/// <summary>
		/// Description of the resource type being provided
		/// </summary>
		private string m_Description = "Provides Tracing Functionality on all events.";
		/// <summary>
		/// Internal flag indicating if this provider instance has been initialzed.
		/// </summary>
		private bool m_Initialized;
#if DEBUG
		/// <summary>
		/// The TraceSource to trace to.
		/// </summary>
		static TraceSource trace;
#endif
		/// <summary>
		/// Initializes a new instance of the <see cref="TraceProvider"/> class.
		/// </summary>
		public TraceProvider() { }
		/// <summary>
		/// Initializes a new instance of the <see cref="TraceProvider"/> class.
		/// </summary>
		/// <param name="useDefaults">if set to <c>true</c> uses default values to Initialize this class and make it ready to Trace.</param>
		public TraceProvider(bool useDefaults)
		{
			if (useDefaults)
			{
				m_Initialized = true;
#if DEBUG
				trace = new TraceSource(m_Name);
#endif
			}
		}
		/// <summary>
		/// Initializes the provider specified in the ProviderType.
		/// </summary>
		/// <param name="providerConfig">The provider config.</param>
		/// <exception cref="System.ArgumentNullException">The name of the provider is null.</exception>
		/// <exception cref="System.ArgumentException">The name of the provider has a length of zero</exception>
		/// <exception cref="System.InvalidOperationException"> An attempt is made to call
		/// MetaCollection.Framework.Provider.IProvider.Initialize(MetaCollection.Framework.Model.IProviderType)
		/// on a provider after the provider has already been initialized</exception>
		public void Initialize(MetaCollection.Framework.Configuration.ProviderReferenceElement providerConfig)
		{
			lock (this)
			{
				if (m_Initialized)
					throw new InvalidOperationException("This provider has already been initialized.");
				m_Initialized = true;
			}
			if (providerConfig == null)
				throw new ArgumentNullException("providerConfig");

			if (String.IsNullOrEmpty(providerConfig.Name))
				throw new ArgumentException("Name of Provider can not be null", "providerConfig");
			m_Name = providerConfig.Name;
#if DEBUG
			trace = new TraceSource(m_Name);
#endif
		}

		/// <summary>
		/// Ensures the provider has been initialized.
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if <see cref="ResourceProvider.Initialize"/> has not been called.</exception>
		private void EnsureInitialized()
		{
			if (!m_Initialized)
				throw new InvalidOperationException("Resource Provider must be initialized prior to use.");
		}

		#region Public Properties
		/// <summary>
		/// Unique Identifier of this provider.
		/// </summary>
		/// <value></value>
		public Guid Id
		{
			get
			{
				EnsureInitialized();
				return m_Id;
			}
		}

		/// <summary>
		/// Gets the name of this provider.
		/// </summary>
		/// <value>The name.</value>
		public string Name
		{
			get
			{
				EnsureInitialized();
				return m_Name;
			}
		}
		/// <summary>
		/// Gets the friendly description of this provider.
		/// </summary>
		/// <value>The description.</value>
		public string Description
		{
			get
			{
				EnsureInitialized();
				return m_Description;
			}
		}

		/// <summary>
		/// Gets the supported events.
		/// </summary>
		/// <value>The supported events.</value>
		public RequestEventTypes SupportedEvents
		{
			get { return RequestEventTypes.AllEvents; }
		}

		/// <summary>
		/// Returns a collection of ProviderType records that describe types which this IProvider
		/// depends on or requires for proper operation.
		/// </summary>
		/// <value>An empty collection.</value>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification="this method can not be static if the class is going to implement the interface")]
		public IEnumerable<Model.IProviderType> DependantProviders
		{
			get { return new List<Model.IProviderType>(); }
		}
		#endregion

		ItemContext ExtractContext(object sender, System.EventArgs args) {
			EnsureInitialized();

			ItemContext context = sender as ItemContext;
			if (context == null)
				throw new InvalidOperationException("The Current Request Context of this request can not be null.");

			return context;
		}

#if DEBUG
		/// <summary>
		/// Traces the event.
		/// </summary>
		/// <param name="source">The source.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		void TraceEvent(string source, object sender, System.EventArgs args)
		{
			ItemContext context = ExtractContext(sender, args);
			trace.TraceInformation("{0}:{1}({2})", m_Name, source, context.RequestPath.AbsolutePath);
			trace.TraceData(TraceEventType.Verbose, 0, context);
		}

		/// <summary>
		/// Registers the listener.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		void RegisterListener(object sender, System.EventArgs args)
		{
			ItemContext context = ExtractContext(sender, args);
			context.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(context_PropertyChanged);
		}

		/// <summary>
		/// Unregisters the listener.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		void UnRegisterListener(object sender, System.EventArgs args)
		{
			ItemContext context = ExtractContext(sender, args);
			context.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(context_PropertyChanged);
		}

		/// <summary>
		/// Handles the PropertyChanged event of the ItemContext.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
		void context_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			trace.TraceEvent(TraceEventType.Verbose, 0, String.Format(CultureInfo.CurrentCulture, "TraceProvider:ItemContext {0} Updated.", e.PropertyName));
			// trace out an error if the Execution Exception was set.
			if (e.PropertyName.Equals("ExecutionException", StringComparison.OrdinalIgnoreCase))
			{
				ItemContext c = (sender as ItemContext);
				if (c != null)
					trace.TraceEvent(TraceEventType.Warning, 0, String.Format(CultureInfo.CurrentCulture, "An Exception Occured During the Processing Of the Request. {0}", c.ExecutionException.ToString()));
				else
					trace.TraceEvent(TraceEventType.Error, 0, "An Error Occured During the Processing of the Request but the Details Could not Be Retrieved.");
			}
		}

		#region IProvider Events
		public void OnRequestStart(object sender, System.EventArgs args)
		{
			TraceEvent("OnRequestStart", sender, args);
			RegisterListener(sender, args);
		}

		public void OnBeforeValidateRequest(object sender, System.EventArgs args)
		{
			TraceEvent("OnBeforeValidateRequest", sender, args);
		}

		public void OnRequestValidateComplete(object sender, System.EventArgs args)
		{
			TraceEvent("OnRequestValidateComplete", sender, args);
		}

		public void OnBeforePathResolve(object sender, System.EventArgs args)
		{
			TraceEvent("OnBeforePathResolve", sender, args);
		}

		public void OnPathResolveComplete(object sender, System.EventArgs args)
		{
			TraceEvent("OnPathResolveComplete", sender, args);
		}

		public void OnBeforeInvokeRequest(object sender, System.EventArgs args)
		{
			TraceEvent("OnBeforeInvokeRequest", sender, args);
		}

		public void OnValidateRequest(object sender, System.EventArgs args)
		{
			TraceEvent("OnValidateRequest", sender, args);
		}

		public void OnPathResolve(object sender, System.EventArgs args)
		{
			TraceEvent("OnPathResolve", sender, args);
		}

		public void OnProvideItem(object sender, System.EventArgs args)
		{
			TraceEvent("OnProvideItem", sender, args);
		}

		public void OnProvideResource(object sender, System.EventArgs args)
		{
			TraceEvent("OnProvideResource", sender, args);
		}

		public void OnHandlerDisposing(object sender, System.EventArgs args)
		{
			TraceEvent("OnHandlerDisposing", sender, args);
		}

		public void OnRequestComplete(object sender, System.EventArgs args)
		{
			TraceEvent("OnRequestComplete", sender, args);
			UnRegisterListener(sender, args);
		}
		#endregion
#else
		public void OnRequestStart(object sender, System.EventArgs args)
		{
		}

		public void OnBeforeValidateRequest(object sender, System.EventArgs args)
		{
		}

		public void OnRequestValidateComplete(object sender, System.EventArgs args)
		{
		}

		public void OnBeforePathResolve(object sender, System.EventArgs args)
		{
		}

		public void OnPathResolveComplete(object sender, System.EventArgs args)
		{
		}

		public void OnBeforeInvokeRequest(object sender, System.EventArgs args)
		{
		}

		public void OnValidateRequest(object sender, System.EventArgs args)
		{
		}

		public void OnPathResolve(object sender, System.EventArgs args)
		{
		}

		public void OnProvideItem(object sender, System.EventArgs args)
		{
		}

		public void OnProvideResource(object sender, System.EventArgs args)
		{
		}

		public void OnHandlerDisposing(object sender, System.EventArgs args)
		{
		}

		public void OnRequestComplete(object sender, System.EventArgs args)
		{
		}
#endif
	}
}
